/*
* Description: 系统性能计数器数据抓取
*/
#include "perf_counter.h"
#include <memory>
#include <psapi.h>
#include <tlhelp32.h>
#include <vector>
#include <deque>
#include <iomanip>
#include <sstream>
#include <pdhmsg.h>
#include "log.h"
#include "text_util/string_proc.h"

#pragma comment(lib, "Pdh.lib")
#pragma comment(lib, "DXGI.lib")

bool PerfCounter::Init()
{
    if (isInited) {
        LOG_DEBUG("performance counter is init, return true");
        return true;
    }
    PerfCounterHandler perfCounterInfo;
    do {
        if (!InnerInitPerfCounter(perfCounterInfo)) {
            LOG_ERR("exec InnerInitPerfCounter failed.");
            break;
        }
        if (!InitDisplayAdapter()) {
            LOG_ERR("exec InitDisplayAdapter failed.");
            break;
        }
        isInited = true;
    } while (false);
    if (isInited) {
        std::lock_guard<std::mutex> lck(mtx);
        handleQuery = perfCounterInfo.handleQuery;
        phCounterMemoryCacheBytes = perfCounterInfo.phCounterMemoryCacheBytes;
        phCounterIODataBytesPerSec = perfCounterInfo.phCounterIODataBytesPerSec;
        phCounterGpuData = perfCounterInfo.phCounterGpuData;
        phCounterAllGpuData = perfCounterInfo.phCounterAllGpuData;
        vecCpuCoreCounter = perfCounterInfo.vecCpuCorePerfCounter;
    }
    LOG_DEBUG("exit PerfCounter::Init(), result = {}", isInited);
    return isInited;
}

bool PerfCounter::InnerInitPerfCounter(PerfCounterHandler& handlerInfo)
{
    bool result = false;
    do {
        if (PdhOpenQuery(0, 0, &handlerInfo.handleQuery) != ERROR_SUCCESS ||
            handlerInfo.handleQuery == NULL) {
            LOG_ERR("exec PdhOpenQuery failed.");
            break;
        }
        // 失败的情况下， 不影响后面的性能计数器执行
        if (PdhAddCounter(handlerInfo.handleQuery,
            StringProc::WStringToString(L"\\Memory\\Cache Bytes").c_str(), 0,
            &handlerInfo.phCounterMemoryCacheBytes) != ERROR_SUCCESS) {
            LOG_ERR("add perfcounter MemoryCacheBytes failed.");
        }
        if (PdhAddCounter(handlerInfo.handleQuery,
            StringProc::WStringToString(L"\\PhysicalDisk(_Total)\\Disk Bytes/sec").c_str(), 0,
            &handlerInfo.phCounterIODataBytesPerSec) != ERROR_SUCCESS) {
            LOG_ERR("add perfcounter Disk Bytes failed.");
        }
        if (PdhAddCounter(handlerInfo.handleQuery,
            StringProc::WStringToString(L"\\GPU Engine(*engtype_3d)\\Utilization Percentage").c_str(), 0,
            &handlerInfo.phCounterGpuData) != ERROR_SUCCESS) {
            LOG_ERR("add perfcounter GPU Engine 3d failed.");
            break;
        }
        if (PdhAddCounter(handlerInfo.handleQuery,
            StringProc::WStringToString(L"\\GPU Engine(*)\\Utilization Percentage").c_str(), 0,
            &handlerInfo.phCounterAllGpuData) != ERROR_SUCCESS) {
            LOG_ERR("add perfcounter GPU Engine(*) failed.");
            break;
        }
        if (!InitCpuCorePerfHandle(handlerInfo.handleQuery, handlerInfo.vecCpuCorePerfCounter)) {
            LOG_ERR("exec InitCpuCorePerfHandle failed.");
            break;
        }
        result = true;
    } while (false);
    return result;
}

int PerfCounter::GetProcessorNum()
{
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    return static_cast<int>(sysInfo.dwNumberOfProcessors);
}

bool PerfCounter::InitCpuCorePerfHandle(const HQUERY inHandleQuery, std::vector<PDH_HCOUNTER>& vecCpuCorePerfCounter)
{
    bool result = false;
    do {
        if (inHandleQuery == NULL) {
            break;
        }
        cpuCoreNum = GetProcessorNum();
        if (cpuCoreNum <= 0) {
            break;
        }
        bool initCpuPerfCounter = true;
        vecCpuCorePerfCounter = std::vector<PDH_HCOUNTER>(cpuCoreNum, NULL);
        for (size_t i = 0; i < vecCpuCorePerfCounter.size(); ++i) {
            std::wstring processCounter = L"\\Processor Information(0,";
            processCounter += std::to_wstring(i);
            processCounter += L")\\% Processor Time";
            if (PdhAddCounter(inHandleQuery, StringProc::WStringToString(processCounter).c_str(), 0,
                &vecCpuCorePerfCounter[i]) != ERROR_SUCCESS) {
                LOG_ERR("init counter {} failed.", StringProc::WStringToString(processCounter).c_str());
                initCpuPerfCounter = false;
                break;
            }
        }
        if (!initCpuPerfCounter) {
            break;
        }
        result = true;
    } while (false);
    return result;
}

double PerfCounter::GetDisplayDeviceMaxGpuUsage(std::map<std::pair<uint32_t, uint32_t>, double>& inGpuInfo)
{
    double maxGpuUsage = 0.0f;
    for (auto& gpu : inGpuInfo) {
        if (gpu.second > maxGpuUsage) {
            maxGpuUsage = gpu.second;
        }
        gpu.second = 0.0f;
    }
    return maxGpuUsage;
}

double PerfCounter::TraversePdhGpuInfo(PDH_FMT_COUNTERVALUE_ITEM* couterValueitems, DWORD itemCount)
{
    if (couterValueitems == nullptr || itemCount == 0) {
        return 0;
    }
    std::map<std::wstring, double> mapProcGpuUsage;
    for (DWORD i = 0; i < itemCount; i++) {
        if (couterValueitems[i].FmtValue.doubleValue <= 0) {
            continue;
        }
        std::vector<std::wstring> splitProcessGpuInfo;
        SplitString(StringProc::StringToWString(couterValueitems[i].szName), splitProcessGpuInfo, L"_");
        if (splitProcessGpuInfo.size() < PerfCommonData::MIN_GPU_ADAPTERLUID_POST) {
            continue;
        }
        std::wstring key = splitProcessGpuInfo[PerfCommonData::GPU_ADAPTERLUID_HIGHPART_POS] +
            L"_" + splitProcessGpuInfo[PerfCommonData::GPU_ADAPTERLUID_LOWPART_POST];

        // LUID找设备
        const auto& deviceItr = mapLuidToDevice.find(key);
        if (deviceItr == mapLuidToDevice.end()) {
            continue;
        }
        // 设备关联使用率
        auto itr = mapDeviceGpuUsage.find(deviceItr->second);
        if (itr != mapDeviceGpuUsage.end()) {
            itr->second += couterValueitems[i].FmtValue.doubleValue;
        }
        // 进程GPU使用率存储
        if (mapProcGpuUsage.find(splitProcessGpuInfo[1]) == mapProcGpuUsage.end()) {
            mapProcGpuUsage[splitProcessGpuInfo[1]] = couterValueitems[i].FmtValue.doubleValue;
        } else {
            mapProcGpuUsage[splitProcessGpuInfo[1]] += couterValueitems[i].FmtValue.doubleValue;
        }
    }
    // 显卡设备中GPU使用率最高者
    return GetDisplayDeviceMaxGpuUsage(mapDeviceGpuUsage);
}

double PerfCounter::GetSystemGpuUsage()
{
    if (phCounterGpuData == NULL) {
        return 0;
    }
    DWORD bufferSize = 0;
    DWORD itemCount = 0;
    PDH_FMT_COUNTERVALUE_ITEM* couterValueitems = NULL;
    PDH_STATUS status = PdhGetFormattedCounterArray(phCounterGpuData, PDH_FMT_DOUBLE, &bufferSize,
        &itemCount, couterValueitems);
    if (status == PDH_MORE_DATA && bufferSize > 0) {
        std::unique_ptr<char[]> data;
        try {
            data = std::make_unique<char[]>(bufferSize + 1);
        } catch (const std::bad_alloc& e) {
            LOG_ERR("system gpu usage bad alloc.");
            return 0;
        }
        SecureZeroMemory(data.get(), bufferSize + 1);
        couterValueitems = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM* >(data.get());
        status = PdhGetFormattedCounterArray(phCounterGpuData, PDH_FMT_DOUBLE, &bufferSize,
            &itemCount, couterValueitems);
        if (status == ERROR_SUCCESS) {
           return TraversePdhGpuInfo(couterValueitems, itemCount);
        }
    }
    return 0;
}

// 按厂商ID和设备ID映射LUID列表， 同样的设备ID和厂商ID对应的LUID列表认为是同一个设备
bool PerfCounter::InitDisplayAdapter()
{
    mapDeviceGpuUsage.clear();
    mapLuidToDevice.clear();
    bool result = GetLuidMapping();
    return result;
}

void PerfCounter::GetPerfCounterValue(PDH_HCOUNTER counter, double& counterValue)
{
    if (counter == NULL) {
        return;
    }
    PDH_FMT_COUNTERVALUE fmtValue = { 0 };
    DWORD  countType = 0;
    if (PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, &countType, &fmtValue) != ERROR_SUCCESS) {
        return;
    }
    counterValue = fmtValue.doubleValue;
}

void PerfCounter::Stop()
{
    std::lock_guard<std::mutex> lck(mtx);
    try {
        if (phCounterMemoryCacheBytes != NULL) {
            (void)PdhRemoveCounter(phCounterMemoryCacheBytes);
            phCounterMemoryCacheBytes = NULL;
        }
        if (phCounterIODataBytesPerSec != NULL) {
            (void)PdhRemoveCounter(phCounterIODataBytesPerSec);
            phCounterIODataBytesPerSec = NULL;
        }
        for (auto& counter : vecCpuCoreCounter) {
            if (counter != NULL) {
                (void)PdhRemoveCounter(counter);
                counter = NULL;
            }
        }
        if (phCounterGpuData != NULL) {
            (void)PdhRemoveCounter(phCounterGpuData);
            phCounterGpuData = NULL;
        }
        if (phCounterAllGpuData != NULL) {
            (void)PdhRemoveCounter(phCounterAllGpuData);
            phCounterAllGpuData = NULL;
        }
        if (isInited && handleQuery != NULL) {
            (void)PdhCloseQuery(handleQuery);
            handleQuery = NULL;
        }
        mapDeviceGpuUsage.clear();
        mapLuidToDevice.clear();
        isInited = false;
    } catch (...) {
        LOG_ERR("PerfCounter stop failed.");
    }
}

std::wstring PerfCounter::GetLuidStringFromAdapterDesc(const DXGI_ADAPTER_DESC& inValue)
{
    std::wostringstream oss;
    oss << L"0x" << std::hex << std::setw(PerfCommonData::HEXADECIMAL_PLACEHOLDER_LENGTH) <<
        std::uppercase << std::setfill(L'0') << inValue.AdapterLuid.HighPart <<
        L"_0x" << std::setw(PerfCommonData::HEXADECIMAL_PLACEHOLDER_LENGTH) << std::setfill(L'0') <<
        std::uppercase << inValue.AdapterLuid.LowPart;
    std::wstring luidString = oss.str();
    return luidString;
}

void PerfCounter::GetLuidToDeviceInfo(const std::vector<IDXGIAdapter* > vecAdapter)
{
    std::vector<std::pair<uint32_t, uint32_t>> gpuDeviceInfo = {};
    for (size_t i = 0; i < vecAdapter.size(); i++) {
        if (vecAdapter[i] == nullptr) {
            continue;
        }
        DXGI_ADAPTER_DESC adapterDesc;
        SecureZeroMemory(&adapterDesc, sizeof(DXGI_ADAPTER_DESC));
        HRESULT innerResult = vecAdapter[i]->GetDesc(&adapterDesc);
        if (innerResult != S_OK) {
            LOG_ERR("GetDesc failed. continue");
            continue;
        }
        // 剔除microsoft的数据
        std::wstring adapterDescription(adapterDesc.Description);
        if (adapterDescription.find(PerfCommonData::MICROSOFT_DISPLAY_ADAPTER_DESCRIPTER) != std::wstring::npos) {
            LOG_ERR("filter description {} because of this device belong microsoft.",
                StringProc::WStringToString(adapterDescription).c_str());
            continue;
        }
        std::wstring luidString = GetLuidStringFromAdapterDesc(adapterDesc);
        if (luidString == L"") {
            LOG_ERR("luidString is empty, continue");
            continue;
        }
        auto gpuDeviceInfoItem = std::pair<uint32_t, uint32_t>({ adapterDesc.VendorId, adapterDesc.DeviceId });
        mapLuidToDevice.insert({ luidString, gpuDeviceInfoItem });
        auto itr = std::find_if(gpuDeviceInfo.begin(), gpuDeviceInfo.end(), [gpuDeviceInfoItem](const auto& info) {
            return (gpuDeviceInfoItem.first == info.first && gpuDeviceInfoItem.second == info.second);
        });
        // 避免存放重复设备
        if (itr == gpuDeviceInfo.end()) {
            std::ostringstream oss;
            oss << "add device luid = " << StringProc::WStringToString(luidString) << " VendorId = 0x" <<
                std::hex << gpuDeviceInfoItem.first << " DeviceId = 0x" << gpuDeviceInfoItem.second;
            gpuDeviceInfo.emplace_back(gpuDeviceInfoItem);
        }
    }
}

bool PerfCounter::InitDeviceGpuUsage()
{
    for (const auto& device : mapLuidToDevice) {
        mapDeviceGpuUsage.insert({ device.second, 0.0f});
    }
    return !mapDeviceGpuUsage.empty();
}

bool PerfCounter::GetLuidMapping()
{
    bool result = false;
    IDXGIFactory* factory = nullptr;
    std::vector<IDXGIAdapter* > vecAdapter = {};
    do {
        HRESULT innerResult = CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&factory));
        if (innerResult != S_OK || factory == nullptr) {
            LOG_ERR("CreateDXGIFactory failed.");
            break;
        }
        int adapterNum = 0;
        IDXGIAdapter* adapter = nullptr;
        while (factory->EnumAdapters(adapterNum, &adapter) != DXGI_ERROR_NOT_FOUND) {
            vecAdapter.push_back(adapter);
            ++adapterNum;
        }
        GetLuidToDeviceInfo(vecAdapter);
        result = InitDeviceGpuUsage();
    } while (false);
    for (const auto& item : vecAdapter) {
        if (item != nullptr) {
            item->Release();
        }
    }
    vecAdapter.clear();
    if (factory != nullptr) {
        factory->Release();
        factory = nullptr;
    }
    return result;
}

PerfCounter::PerfCounter()
{
}

PerfCounter::~PerfCounter()
{
    Stop();
}

PerfCounter &PerfCounter::GetInstance()
{
    static PerfCounter instance;
    return instance;
}

void PerfCounter::GetProcessThreadAndHandleInfo(
    unsigned long long &totalProcess, unsigned long long &totalThread, unsigned long long &totalHandle)
{
    std::vector<ProcessThreadAndHandleInfo> vecProcessThreadAndHandleInfo = {};
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) {
        return;
    }
    std::unique_ptr<void, std::function<void(HANDLE)>> autoCloseHandle1(
        snapshot, std::bind(&PerfCounter::AutoCloseHandle, this, std::placeholders::_1));
    PROCESSENTRY32W procEntry;
    procEntry.dwSize = sizeof(procEntry);
    BOOL status = Process32FirstW(snapshot, &procEntry);
    while (status == TRUE) {
        DWORD procId = procEntry.th32ProcessID;
        if (procId == 0 || procId == PerfCommonData::WINDOWS_SYSTEM_PROCESS_PID) {
            status = Process32NextW(snapshot, &procEntry);
            continue;
        }
        HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, procEntry.th32ProcessID);
        std::unique_ptr<void, std::function<void(HANDLE)>> autoCloseHandle2(
            process, std::bind(&PerfCounter::AutoCloseHandle, this, std::placeholders::_1));
        if (process != NULL) {
            DWORD processHandleCount = 0;
            if (GetProcessHandleCount(process, &processHandleCount) != FALSE) {
                ProcessThreadAndHandleInfo procResItem;
                procResItem.pid = procEntry.th32ProcessID;
                procResItem.processName = procEntry.szExeFile;
                procResItem.threadNum = procEntry.cntThreads;
                procResItem.handleNum = processHandleCount;
                vecProcessThreadAndHandleInfo.push_back(procResItem);
            }
        }
        status = Process32NextW(snapshot, &procEntry);
    }
    CloseHandle(snapshot);
    if (!vecProcessThreadAndHandleInfo.empty()) {
        for (const auto& proc : vecProcessThreadAndHandleInfo) {
            totalProcess += 1;
            totalThread += proc.threadNum;
            totalHandle += proc.handleNum;
        }
    }
}

void PerfCounter::AutoCloseHandle(HANDLE handle)
{
    if (handle != NULL) {
        CloseHandle(handle);
    }
}

std::vector<double> PerfCounter::GetCpuPerfCounterData()
{
    std::vector<double> vecCpuCoreCountTemp = {};
    for (size_t i = 0; i < vecCpuCoreCounter.size(); ++i) {
        if (vecCpuCoreCounter[i] != NULL) {
            PDH_FMT_COUNTERVALUE fmtValue = { 0 };
            DWORD countType = 0;
            if (PdhGetFormattedCounterValue(vecCpuCoreCounter[i], PDH_FMT_DOUBLE, &countType,
                &fmtValue) == ERROR_SUCCESS) {
                vecCpuCoreCountTemp.push_back(fmtValue.doubleValue);
            }
        }
    }
    return vecCpuCoreCountTemp;
}

double PerfCounter::GetIOSpeed()
{
    std::lock_guard<std::mutex> lck(mtx);
    if (!isInited) {
        LOG_ERR("performance counter init failed.");
        return 0;
    }
    if (PdhCollectQueryData(handleQuery) != ERROR_SUCCESS) {
        LOG_ERR("PdhCollectQueryData execute failed.");
        return 0;
    }
    double ioDataBytesPerSec = 0.0f;
    GetPerfCounterValue(phCounterIODataBytesPerSec, ioDataBytesPerSec);
    return ioDataBytesPerSec;
}

void PerfCounter::SplitString(
    const std::wstring& srcValue, std::vector<std::wstring>& result, const std::wstring& subValue)
{
    std::string::size_type pos1 = 0;
    std::string::size_type pos2 = srcValue.find(subValue);

    while (pos2 != std::string::npos) {
        result.push_back(srcValue.substr(pos1, pos2 - pos1));

        pos1 = pos2 + subValue.size();
        pos2 = srcValue.find(subValue, pos1);
    }

    if (pos1 != srcValue.length()) {
        result.push_back(srcValue.substr(pos1));
    }
}